package nfa;

import dfa.DFADesign;
import dfa.DFARulebook;
import fa.FARule;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * @author haoly
 * @title NFASimulation
 * @date 2025/4/17 19:16
 * @description NFA模拟类
 */
public class NFASimulation<T> {
    private final NFADesign<T> nfaDesign;

    public NFASimulation(NFADesign<T> nfaDesign) {
        this.nfaDesign = nfaDesign;
    }

    public NFADesign<T> getNfaDesign() {
        return nfaDesign;
    }

    public Set<T> nextState(Set<T> state, Character character){
        NFA<T> nfa = nfaDesign.toNFA(state);
        nfa.readCharacter(character);
        return nfa.getCurrentStates();
    }

    public List<FARule<Set<T>>> rulesFor(Set<T> state){
        List<FARule<Set<T>>> result = new ArrayList<>();

        Set<Character> alphabet = this.nfaDesign.getRulebook().alphabet();
        for (Character character : alphabet) {
            FARule<Set<T>> rule = new FARule<>(state, character, nextState(state, character));
            result.add(rule);
        }

        return result;
    }

    public List<Object> discoverStatesAndRules(Set<Set<T>> states){
        List<FARule<Set<T>>> rules = new ArrayList<>();
        for (Set<T> state : states) {
            rules.addAll(rulesFor(state));
        }

        Set<Set<T>> moreStates = new HashSet<>();
        for (FARule<Set<T>> rule : rules) {
            moreStates.add(rule.getNextState());
        }

        if (states.containsAll(moreStates)) {
            List<Object> result = new ArrayList<>();
            result.add(states);
            result.add(rules);
            return result;
        }else {
            Set<Set<T>> nextStates = new HashSet<>(states);
            nextStates.addAll(moreStates);
            return discoverStatesAndRules(nextStates);
        }
    }

    public DFADesign<Set<T>> toDFADesign(){
        Set<T> startState = this.nfaDesign.toNFA().getCurrentStates();
        List<Object> result = discoverStatesAndRules(Set.of(startState));
        Set<Set<T>> states = (Set<Set<T>>)result.get(0);
        List<FARule<Set<T>>> rules = (List<FARule<Set<T>>>)result.get(1);
        Set<Set<T>> acceptStates = new HashSet<>();
        for (Set<T> state : states) {
            if (this.nfaDesign.toNFA(state).accepting()) {
                acceptStates.add(state);
            }
        }
        DFARulebook<Set<T>> rulebook = new DFARulebook<>(rules);
        return new DFADesign<>(startState, acceptStates, rulebook);



    }
}
